<!DOCTYPE html>
<html class="theme-dark">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>F3D Web</title>
  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-switch@2.0.4/dist/css/bulma-switch.min.css">
  <style>
    #main {
      min-height: 70vh;
    }
  </style>
</head>

<body>
  <section class="section">
    <div class="columns">
      <div class="column">
        <h1 class="title">F3D Web</h1>
        <p class="subtitle">
          A WebAssembly application using libf3d
        </p>
      </div>
      <div class="column">
        <div class="field is-pulled-right">
          <input id="dark" type="checkbox" name="dark" class="switch is-rounded" checked>
          <label for="dark">Dark theme</label>
        </div>
      </div>
    </div>
  </section>
  <section class="section">
    <div class="columns">
      <aside class="menu">
        <div class="file has-name">
          <label class="file-label">
            <input class="file-input" type="file" id="file-selector" accept=".gml,.gltf,.glb,.obj,.ply,.pts,.stl,.vtk,.vtp,.vtu,.3ds,.wrl,.vrml,.fbx,.off,.x,.dae,.stp,.step,.igs,.iges,.brep,.xbf,.drc" />
            <span class="file-cta">
              <span class="file-label">Open a file...</span>
            </span>
            <span class="file-name" id="file-name"></span>
          </label>
        </div>
        <div class="is-flex is-flex-direction-row is-align-items-center">
          <button id="y-up" class="button mx-1 is-small">+Y</button>
          <button id="z-up" class="button mx-1 is-small is-active">+Z</button>
          <label class="mx-1">Up</label>
        </div>
        <p class="menu-label">Widgets</p>
        <div class="field">
          <input id="grid" type="checkbox" name="grid" class="switch is-rounded" checked>
          <label for="grid">Grid</label>
        </div>
        <div class="field">
          <input id="axis" type="checkbox" name="axis" class="switch is-rounded" checked>
          <label for="axis">Axis</label>
        </div>
        <p class="menu-label">Rendering</p>
        <div class="field">
          <input id="fxaa" type="checkbox" name="fxaa" class="switch is-rounded" checked>
          <label for="fxaa">Anti-aliasing</label>
        </div>
        <div class="field">
          <input id="tone" type="checkbox" name="tone" class="switch is-rounded" checked>
          <label for="tone">Tone mapping</label>
        </div>
        <div class="field">
          <input id="ssao" type="checkbox" name="ssao" class="switch is-rounded" checked>
          <label for="ssao">Ambient occlusion</label>
        </div>
        <p class="menu-label">Lighting</p>
        <div class="field">
          <input id="ambient" type="checkbox" name="ambient" class="switch is-rounded" checked>
          <label for="ambient">Ambient light</label>
        </div>
      </aside>
      <div class="column">
        <div class="container" id="main">
          <canvas id="canvas"></canvas>
        </div>
      </div>
    </div>
  </section>
  <script type="text/javascript" src="f3d.js"></script>
  <script type="text/javascript">

    const settings = {
      canvas: document.getElementById("canvas"),
      setupOptions: (options) => {
        // background must be set to black for proper blending with transparent canvas
        options.set_color('render.background.color', 0, 0, 0);

        // setup coloring
        options.toggle('model.scivis.enable');
        options.set_string('model.scivis.array_name', 'Colors');
        options.set_integer('model.scivis.component', -2);
        options.toggle('model.scivis.cells');

        // make it look nice
        options.toggle('render.effect.anti_aliasing');
        options.toggle('render.effect.tone_mapping');
        options.toggle('render.effect.ambient_occlusion');
        options.toggle('render.hdri.ambient');

        // display widgets
        options.toggle('interactor.axis');
        options.toggle('render.grid.enable');

        // default to +Z
        options.set_string('scene.up_direction', '+Z');
      }
    };

    f3d(settings)
      .then((Module) => {
        // automatically load all supported file format readers
        Module.Engine.autoloadPlugins();

        Module.engineInstance = Module.Engine.create()

        const openFile = (name) => {
          document.getElementById('file-name').innerHTML = name;
          const filePath = '/' + name;
          const scene = Module.engineInstance.getScene();
          if (scene.supports(filePath))
          {
            scene.clear();
            scene.add(filePath);
          }
          else
          {
            console.error('File ' + filePath + ' cannot be opened');
          }
          Module.engineInstance.getWindow().resetCamera();
          Module.engineInstance.getWindow().render();
        };

        // setup file open event
        document.querySelector('#file-selector')
          .addEventListener('change', (evt) => {
            for (const file of evt.target.files) {
              const reader = new FileReader();
              reader.addEventListener('loadend', (e) => {
                Module.FS.writeFile(file.name, new Uint8Array(reader.result));
                openFile(file.name);
              });
              reader.readAsArrayBuffer(file);
            }
          });

        Module.setupOptions(Module.engineInstance.getOptions());

        // toggle callback
        const mapToggleIdToOption = (id, option) => {
          document.querySelector('#' + id)
            .addEventListener('change', (evt) => {
              Module.engineInstance.getOptions().toggle(option);
              Module.engineInstance.getWindow().render();
            });
        };

        mapToggleIdToOption('grid', 'render.grid.enable');
        mapToggleIdToOption('axis', 'interactor.axis');
        mapToggleIdToOption('fxaa', 'render.effect.anti_aliasing');
        mapToggleIdToOption('tone', 'render.effect.tone_mapping');
        mapToggleIdToOption('ssao', 'render.effect.ambient_occlusion');
        mapToggleIdToOption('ambient', 'render.hdri.ambient');

        switchDark = () => {
          document.documentElement.classList.add('theme-dark');
          document.documentElement.classList.remove('theme-light');
          Module.engineInstance.getOptions().set_color('render.grid.color', 0.25, 0.27, 0.33);
          Module.engineInstance.getWindow().render();
        };

        switchLight = () => {
          document.documentElement.classList.add('theme-light');
          document.documentElement.classList.remove('theme-dark');
          Module.engineInstance.getOptions().set_color('render.grid.color', 0.67, 0.69, 0.75);
          Module.engineInstance.getWindow().render();
        };

        // theme switch
        document.querySelector('#dark').addEventListener('change', (evt) => {
            if (evt.target.checked)
              switchDark();
            else
              switchLight();
          });

        switchDark();

        // up callback
        document.querySelector('#z-up')
          .addEventListener('click', (evt) => {
            Module.engineInstance.getOptions().set_string('scene.up_direction', '+Z');
            document.getElementById('z-up').classList.add('is-active');
            document.getElementById('y-up').classList.remove('is-active');
            openFile(document.getElementById('file-name').innerHTML);
          });

        document.querySelector('#y-up')
          .addEventListener('click', (evt) => {
            Module.engineInstance.getOptions().set_string('scene.up_direction', '+Y');
            document.getElementById('y-up').classList.add('is-active');
            document.getElementById('z-up').classList.remove('is-active');
            openFile(document.getElementById('file-name').innerHTML);
          });


        // setup the window size based on the canvas size
        const main = document.getElementById('main');
        const scale = window.devicePixelRatio;
        Module.engineInstance.getWindow().setSize(scale * main.clientWidth, scale * main.clientHeight);

        // do a first render and start the interactor
        Module.engineInstance.getWindow().render();
        Module.engineInstance.getInteractor().start();

        // Parse model from url-param search query hash via model url and extension
        function filename_for_model_url(model_url, extension_parsed, contentDisposition) {
          // Build filename given extension urlparam or response header content-disposition
          if (extension_parsed) {
            return `model_urlparam.${extension_parsed}`;
          } else if (contentDisposition) {
            // If extension is not provided by user, try to get it auto from content-disposition header of url extension 
            return contentDisposition.split('filename=')[1].split(';')[0];
          } else {
            return model_url.split('/').pop();
          }
          throw new Error(`Could not parse filename/extension from either urlparam extension, response header content-disposition, nor filename present in url`);
        }
        function load_from_url(){
          // Parse search-query model url-param or load default model file
          // const params = new URLSearchParams(window.location.search);
          // Replace first hash with question mark to have real search query parsing and avoid leading # in first parsed urlparam
          const params = new URLSearchParams(window.location.hash.replace(/^#/, '?'));
          const model_url_passed = params.get("model");
          const extension_parsed = params.get("extension");
          if (model_url_passed) {
            const model_url = decodeURI(model_url_passed);
            fetch(model_url)
            .then((response) => {
              if (!response.ok) {
                throw new Error(`HTTP error, status = ${response.status}`);
              }
              const contentDisposition = response.headers.get('content-disposition');
              const filename = filename_for_model_url(model_url, extension_parsed, contentDisposition);
              // Convert buffer to Uint8Array and openFile
              response.arrayBuffer().then((buffer) => {
                Module.FS.writeFile(filename, new Uint8Array(buffer));
                openFile(filename);
              });
            })
          } else {
            // load the file located in the virtual filesystem
            openFile('f3d.vtp');
          }
        }
        addEventListener("hashchange", (event) => {load_from_url()}); 
        load_from_url(); 
      })
      .catch(error => console.error("Internal exception: " + error));
  </script>
</body>

</html>
